package br.unicamp.integralization.request;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.AsyncTask;

/**
 * The Class RequestTask used to handle requests and it's returns.
 */
public abstract class RequestTask extends AsyncTask<String, Object, RequestDTO> {
	public static final String METHOD_GET = "GET";
	public static final String METHOD_POST = "POST";

	/** If true this class will handle error. */
	boolean handleError = true;

	/** If true this class will handle error connection. */
	boolean handleErrorConnection = true;

	/**
	 * Configurate the object to do the request, must be called.
	 * 
	 * @param handleError
	 *            the handle error
	 * @param handleErrorConnection
	 *            the handle error connection
	 * @return the request task
	 */
	public RequestTask config(boolean handleError, boolean handleErrorConnection) {
		this.handleError = handleError;
		this.handleErrorConnection = handleErrorConnection;
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.os.AsyncTask#doInBackground(Params[])
	 */
	@Override
	protected RequestDTO doInBackground(String... urls) {

		// params comes from the execute() call: params[0] is the url and
		// params[1] is the method.
		try {
			return downloadUrl(urls[0], (urls.length > 1 ? urls[1] : "GET"),
					(urls.length > 2 ? urls[2] : null));
		} catch (Exception e) {
			return new RequestDTO(RequestStatusEnum.CONNECTION_ERROR.code(),
					null, null);
		}
	}

	/**
	 * Given a URL and http method, establishes an HttpUrlConnection and
	 * retrieves the web page content as a InputStream, which it returns as a
	 * string.
	 * 
	 * @param myurl
	 *            the myurl
	 * @param method
	 *            the method
	 * @return the string
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private RequestDTO downloadUrl(String myurl, String method, String params)
			throws IOException {
		InputStream is = null;
		// Only display the first 5000 characters of the retrieved
		// web page content.
		try {
			// define method
			method = ValidationHelper.isEmptyOrVoid(method) ? "GET" : method;

			// variables
			URL url;

			if (method.equals("GET")) {
				url = new URL(myurl + "?" + params);
			} else {
				url = new URL(myurl);
			}
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setReadTimeout(20000);// milliseconds
			conn.setConnectTimeout(40000);// milliseconds
			conn.setRequestMethod(method);
			conn.setDoInput(true);

			// write post params
			if (method.equals("POST")) {

				conn.setRequestProperty("Accept-Charset", "UTF-8");
				conn.setRequestProperty("Content-Type",
						"application/x-www-form-urlencoded;charset=UTF-8");
				conn.setDoOutput(true);
				OutputStream output = null;
				try {
					output = conn.getOutputStream();
					output.write(params.getBytes("UTF-8"));
				} finally {
					if (output != null)
						try {
							output.close();
						} catch (IOException logOrIgnore) {
						}
				}

			}

			// Starts the query
			conn.connect();
			int response = conn.getResponseCode();
			String contentAsString = "";

			if (HttpURLConnection.HTTP_OK == response) {
				try {

					is = conn.getInputStream();

					// Convert the InputStream into a string
					contentAsString = readIt(is);

					// try to read as a string, if valid return success
					if (ValidationHelper.isValid(contentAsString,
							ValidationConstants.NUMBER)) {
						return new RequestDTO(RequestStatusEnum.OK.code(),
								contentAsString, null);
					}

					// try to read the success json
					JSONObject jObject = new JSONObject(contentAsString);
					try {
						Boolean ret = jObject.getBoolean("success");

						if (ret != null && !ret) {
							return new RequestDTO(
									RequestStatusEnum.ERROR.code(), jObject
											.getJSONObject("error").getString(
													"msg"), jObject
											.getJSONObject("error").getString(
													"code"));
						} else {
							return new RequestDTO(RequestStatusEnum.OK.code(),
									contentAsString, null);
						}
					} catch (JSONException e) {
						// if couldn't read success attribute return OK because
						// contentAsString is a JSONObject
						return new RequestDTO(RequestStatusEnum.OK.code(),
								contentAsString, null);
					}

				} catch (JSONException e) {

					try {
						// try to read the success json as an array
						new JSONArray(contentAsString);

						// if couldn't read success attribute return OK because
						// contentAsString is a JSONObject
						return new RequestDTO(RequestStatusEnum.OK.code(),
								contentAsString, null);
					} catch (JSONException exception) {
						// json isn't in the expected format
						return new RequestDTO(RequestStatusEnum.ERROR.code(),
								contentAsString, null);
					} catch (Exception exception) {
						return new RequestDTO(
								RequestStatusEnum.CONNECTION_ERROR.code(),
								contentAsString, null);
					}

				} catch (Exception e) {
					return new RequestDTO(
							RequestStatusEnum.CONNECTION_ERROR.code(),
							contentAsString, null);
				}
			} else if (HttpURLConnection.HTTP_INTERNAL_ERROR == response) {
				try {
					is = conn.getErrorStream();

					// Convert the InputStream into a string
					contentAsString = readIt(is);

					// try to read the error json
					JSONObject jObject = new JSONObject(contentAsString);
					return new RequestDTO(RequestStatusEnum.ERROR.code(),
							jObject.getJSONObject("error").getString("msg"),
							jObject.getJSONObject("error").getString("code"));
				} catch (Exception e) {
					return new RequestDTO(
							RequestStatusEnum.CONNECTION_ERROR.code(), null,
							null);
				}
			} else {

				return new RequestDTO(
						RequestStatusEnum.CONNECTION_ERROR.code(), null, null);
			}

		} catch (Exception e) {
			return new RequestDTO(RequestStatusEnum.CONNECTION_ERROR.code(),
					null, null);
		} finally {

			// Makes sure that the InputStream is closed after the app is
			// finished using it.
			if (is != null) {
				is.close();
			}
		}
	}

	/**
	 * Reads an InputStream and converts it to a String.
	 * 
	 * @param stream
	 *            the stream
	 * @param len
	 *            the len
	 * @return the string
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws UnsupportedEncodingException
	 *             the unsupported encoding exception
	 */
	public String readIt(InputStream stream) throws IOException,
			UnsupportedEncodingException {

		// read it with BufferedReader
		BufferedReader br = new BufferedReader(new InputStreamReader(stream));

		StringBuilder sb = new StringBuilder();

		String line;
		while ((line = br.readLine()) != null) {
			sb.append(line);
		}

		return sb.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
	 */
	@Override
	protected abstract void onPostExecute(RequestDTO result);

}
